Implement a local registry type
authorAlex Crichton <alex@alexcrichton.com>
Fri, 5 Feb 2016 23:14:17 +0000 (15:14 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 1 Aug 2016 17:14:52 +0000 (10:14 -0700)
This flavor of registry is intended to behave very similarly to the standard
remote registry, except everything is contained locally on the filesystem
instead. There are a few components to this new flavor of registry:

1. The registry itself is rooted at a particular directory, owning all structure
   beneath it.
2. There is an `index` folder with the same structure as the crates.io index
   describing the local registry (e.g. contents, versions, checksums, etc).
3. Inside the root will also be a list of `.crate` files which correspond to
   those described in the index. All crates must be of the form
   `name-version.crate` and be the same `.crate` files from crates.io itself.

This support can currently be used via the previous implementation of source
overrides with the new type:

```toml
[source.crates-io]
replace-with = 'my-awesome-registry'

[source.my-awesome-registry]
local-registry = 'path/to/registry'
```

I will soon follow up with a tool which can be used to manage these local
registries externally.

src/cargo/core/source.rs
src/cargo/sources/config.rs
src/cargo/sources/registry/index.rs
src/cargo/sources/registry/local.rs [new file with mode: 0644]
src/cargo/sources/registry/mod.rs
src/cargo/sources/registry/remote.rs
tests/bad-config.rs
tests/cargotest/support/mod.rs
tests/cargotest/support/registry.rs
tests/local-registry.rs [new file with mode: 0644]
tests/registry.rs

index b8b89f600d9207822e1df3c53b7a477c5005e6c9..93e223a29e554a90230646a13f543a173d9a6552 100644 (file)
@@ -62,6 +62,8 @@ enum Kind {
     Path,
     /// represents the central registry
     Registry,
+    /// represents a local filesystem-based registry
+    LocalRegistry,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -167,6 +169,9 @@ impl SourceId {
             SourceIdInner { kind: Kind::Registry, ref url, .. } => {
                 format!("registry+{}", url)
             }
+            SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
+                format!("local-registry+{}", url)
+            }
         }
     }
 
@@ -184,6 +189,11 @@ impl SourceId {
         SourceId::new(Kind::Registry, url.clone())
     }
 
+    pub fn for_local_registry(path: &Path) -> CargoResult<SourceId> {
+        let url = try!(path.to_url());
+        Ok(SourceId::new(Kind::LocalRegistry, url))
+    }
+
     /// Returns the `SourceId` corresponding to the main repository.
     ///
     /// This is the main cargo registry by default, but it can be overridden in
@@ -213,7 +223,7 @@ impl SourceId {
         self.inner.kind == Kind::Path
     }
     pub fn is_registry(&self) -> bool {
-        self.inner.kind == Kind::Registry
+        self.inner.kind == Kind::Registry || self.inner.kind == Kind::LocalRegistry
     }
 
     pub fn is_git(&self) -> bool {
@@ -236,6 +246,13 @@ impl SourceId {
                 Box::new(PathSource::new(&path, self, config))
             }
             Kind::Registry => Box::new(RegistrySource::remote(self, config)),
+            Kind::LocalRegistry => {
+                let path = match self.inner.url.to_file_path() {
+                    Ok(p) => p,
+                    Err(()) => panic!("path sources cannot be remote"),
+                };
+                Box::new(RegistrySource::local(self, &path, config))
+            }
         }
     }
 
@@ -321,7 +338,8 @@ impl fmt::Display for SourceId {
                 }
                 Ok(())
             }
-            SourceIdInner { kind: Kind::Registry, ref url, .. } => {
+            SourceIdInner { kind: Kind::Registry, ref url, .. } |
+            SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
                 write!(f, "registry {}", url)
             }
         }
index 0cde9e5e13feca46033e78caeeffbee14460ac04..3be5156452c90badda2e3f12743696f1c030cca5 100644 (file)
@@ -128,11 +128,21 @@ a lock file compatible with `{orig}` cannot be generated in this situation
             let url = try!(url(val, &format!("source.{}.registry", name)));
             srcs.push(SourceId::for_registry(&url));
         }
+        if let Some(val) = table.get("local-registry") {
+            let (s, path) = try!(val.string(&format!("source.{}.local-registry",
+                                                     name)));
+            let mut path = path.to_path_buf();
+            path.pop();
+            path.pop();
+            path.push(s);
+            srcs.push(try!(SourceId::for_local_registry(&path)));
+        }
 
         let mut srcs = srcs.into_iter();
         let src = try!(srcs.next().chain_error(|| {
-            human(format!("no source URL specified for `source.{}`, needs \
-                           `registry` defined", name))
+            human(format!("no source URL specified for `source.{}`, need \
+                           either `registry` or `local-registry` defined",
+                          name))
         }));
         if srcs.next().is_some() {
             return Err(human(format!("more than one source URL specified for \
index 7d06ee1af03e291a50324923b30dd733607ff327..4aa59aebc8ba2ac087a395324c430608d1d7978a 100644 (file)
@@ -1,6 +1,6 @@
 use std::collections::HashMap;
 use std::io::prelude::*;
-use std::fs::File;
+use std::fs::{File, OpenOptions};
 use std::path::Path;
 
 use rustc_serialize::json;
@@ -52,6 +52,13 @@ impl<'cfg> RegistryIndex<'cfg> {
         if self.cache.contains_key(name) {
             return Ok(self.cache.get(name).unwrap());
         }
+        // If the lock file doesn't already exist then this'll cause *someone*
+        // to create it. We don't actually care who creates it, and if it's
+        // already there this should have no effect.
+        drop(OpenOptions::new()
+                        .write(true)
+                        .create(true)
+                        .open(self.path.join(INDEX_LOCK).into_path_unlocked()));
         let lock = self.path.open_ro(Path::new(INDEX_LOCK),
                                      self.config,
                                      "the registry index");
diff --git a/src/cargo/sources/registry/local.rs b/src/cargo/sources/registry/local.rs
new file mode 100644 (file)
index 0000000..6c2966e
--- /dev/null
@@ -0,0 +1,92 @@
+use std::io::SeekFrom;
+use std::io::prelude::*;
+use std::path::Path;
+
+use rustc_serialize::hex::ToHex;
+
+use core::PackageId;
+use sources::registry::{RegistryData, RegistryConfig};
+use util::{Config, CargoResult, ChainError, human, Sha256, Filesystem};
+use util::FileLock;
+
+pub struct LocalRegistry<'cfg> {
+    index_path: Filesystem,
+    root: Filesystem,
+    src_path: Filesystem,
+    config: &'cfg Config,
+}
+
+impl<'cfg> LocalRegistry<'cfg> {
+    pub fn new(root: &Path,
+               config: &'cfg Config,
+               name: &str) -> LocalRegistry<'cfg> {
+        LocalRegistry {
+            src_path: config.registry_source_path().join(name),
+            index_path: Filesystem::new(root.join("index")),
+            root: Filesystem::new(root.to_path_buf()),
+            config: config,
+        }
+    }
+}
+
+impl<'cfg> RegistryData for LocalRegistry<'cfg> {
+    fn index_path(&self) -> &Filesystem {
+        &self.index_path
+    }
+
+    fn config(&self) -> CargoResult<Option<RegistryConfig>> {
+        // Local registries don't have configuration for remote APIs or anything
+        // like that
+        Ok(None)
+    }
+
+    fn update_index(&mut self) -> CargoResult<()> {
+        // Nothing to update, we just use what's on disk. Verify it actually
+        // exists though. We don't use any locks as we're just checking whether
+        // these directories exist.
+        let root = self.root.clone().into_path_unlocked();
+        if !root.is_dir() {
+            bail!("local registry path is not a directory: {}",
+                  root.display())
+        }
+        let index_path = self.index_path.clone().into_path_unlocked();
+        if !index_path.is_dir() {
+            bail!("local registry index path is not a directory: {}",
+                  index_path.display())
+        }
+        Ok(())
+    }
+
+    fn download(&mut self, pkg: &PackageId, checksum: &str)
+                -> CargoResult<FileLock> {
+        let crate_file = format!("{}-{}.crate", pkg.name(), pkg.version());
+        let mut crate_file = try!(self.root.open_ro(&crate_file,
+                                                    self.config,
+                                                    "crate file"));
+
+        // If we've already got an unpacked version of this crate, then skip the
+        // checksum below as it is in theory already verified.
+        let dst = format!("{}-{}", pkg.name(), pkg.version());
+        if self.src_path.join(dst).into_path_unlocked().exists() {
+            return Ok(crate_file)
+        }
+
+        try!(self.config.shell().status("Unpacking", pkg));
+
+        // We don't actually need to download anything per-se, we just need to
+        // verify the checksum matches the .crate file itself.
+        let mut data = Vec::new();
+        try!(crate_file.read_to_end(&mut data).chain_error(|| {
+            human(format!("failed to read `{}`", crate_file.path().display()))
+        }));
+        let mut state = Sha256::new();
+        state.update(&data);
+        if state.finish().to_hex() != checksum {
+            bail!("failed to verify the checksum of `{}`", pkg)
+        }
+
+        try!(crate_file.seek(SeekFrom::Start(0)));
+
+        Ok(crate_file)
+    }
+}
index 4b280c0a9831c548caf2e269544c9c82943e1533..88128f119332f53ab57fc8472d87357286edfcca 100644 (file)
 
 use std::collections::HashMap;
 use std::fs::File;
-use std::path::PathBuf;
+use std::path::{PathBuf, Path};
 
 use flate2::read::GzDecoder;
 use tar::Archive;
@@ -227,6 +227,7 @@ pub trait RegistryData {
 
 mod index;
 mod remote;
+mod local;
 
 fn short_name(id: &SourceId) -> String {
     let hash = hex::short_hash(id);
@@ -242,6 +243,14 @@ impl<'cfg> RegistrySource<'cfg> {
         RegistrySource::new(source_id, config, &name, Box::new(ops))
     }
 
+    pub fn local(source_id: &SourceId,
+                 path: &Path,
+                 config: &'cfg Config) -> RegistrySource<'cfg> {
+        let name = short_name(source_id);
+        let ops = local::LocalRegistry::new(path, config, &name);
+        RegistrySource::new(source_id, config, &name, Box::new(ops))
+    }
+
     fn new(source_id: &SourceId,
            config: &'cfg Config,
            name: &str,
index 28d6ada651a399ca764d2e9c20a122b96ebe1a84..65f09a25fa2356d32053590311e53ba6bb3357c5 100644 (file)
@@ -53,6 +53,10 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
     }
 
     fn update_index(&mut self) -> CargoResult<()> {
+        // Ensure that this'll actually succeed later on.
+        try!(ops::http_handle(self.config));
+
+        // Then we actually update the index
         try!(self.index_path.create_dir());
         let lock = try!(self.index_path.open_rw(Path::new(INDEX_LOCK),
                                                 self.config,
index d7d0192bd16f25eddc23937ec8c70c34341b49a3..15324711fbbdf20abbe14f01f4227fb2379fa437 100644 (file)
@@ -77,8 +77,10 @@ fn bad3() {
 
     assert_that(foo.cargo_process("publish").arg("-v"),
                 execs().with_status(101).with_stderr("\
-[UPDATING] registry `[..]`
-[ERROR] invalid configuration for key `http.proxy`
+error: failed to update registry [..]
+
+Caused by:
+  invalid configuration for key `http.proxy`
 expected a string, but found a boolean for `http.proxy` in [..]config
 "));
 }
@@ -555,7 +557,7 @@ fn bad_source_config1() {
 
     assert_that(p.cargo_process("build"),
                 execs().with_status(101).with_stderr("\
-error: no source URL specified for `source.foo`, needs [..]
+error: no source URL specified for `source.foo`, need [..]
 "));
 }
 
@@ -759,7 +761,6 @@ This will be considered an error in future versions"));
 }
 
 #[test]
-#[ignore]
 fn bad_source_config7() {
     let p = project("foo")
         .file("Cargo.toml", r#"
@@ -775,7 +776,7 @@ fn bad_source_config7() {
         .file(".cargo/config", r#"
             [source.foo]
             registry = 'http://example.com'
-            directory = 'file:///another/file'
+            local-registry = 'file:///another/file'
         "#);
 
     Package::new("bar", "0.1.0").publish();
@@ -785,28 +786,3 @@ fn bad_source_config7() {
 error: more than one source URL specified for `source.foo`
 "));
 }
-
-#[test]
-#[ignore]
-fn bad_source_config8() {
-    let p = project("foo")
-        .file("Cargo.toml", r#"
-            [package]
-            name = "foo"
-            version = "0.0.0"
-            authors = []
-
-            [dependencies]
-            bar = "*"
-        "#)
-        .file("src/lib.rs", "")
-        .file(".cargo/config", r#"
-            [source.foo]
-            directory = 'file://another/file'
-        "#);
-
-    assert_that(p.cargo_process("build"),
-                execs().with_status(101).with_stderr("\
-error: failed to convert `file://another/file` to an absolute path (configured in [..])
-"));
-}
index f493faef6adf5cbffede90a1beaf150227e458df..96f8d2baf4c9030a365ccaafd3fc561b09ddd38b 100644 (file)
@@ -652,7 +652,8 @@ fn substitute_macros(input: &str) -> String {
         ("[VERIFYING]",   "   Verifying"),
         ("[ARCHIVING]",   "   Archiving"),
         ("[INSTALLING]",  "  Installing"),
-        ("[REPLACING]",   "   Replacing")
+        ("[REPLACING]",   "   Replacing"),
+        ("[UNPACKING]",   "   Unpacking"),
     ];
     let mut result = input.to_owned();
     for &(pat, subst) in macros.iter() {
index 26663e2606e16e9beeedd38b3247c8ec4ec6c9d4..da3e12bbff7ec1bdd3d58593d697b8834f5596cd 100644 (file)
@@ -27,6 +27,7 @@ pub struct Package {
     files: Vec<(String, String)>,
     yanked: bool,
     features: HashMap<String, Vec<String>>,
+    local: bool,
 }
 
 struct Dependency {
@@ -74,9 +75,15 @@ impl Package {
             files: Vec::new(),
             yanked: false,
             features: HashMap::new(),
+            local: false,
         }
     }
 
+    pub fn local(&mut self, local: bool) -> &mut Package {
+        self.local = local;
+        self
+    }
+
     pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
         self.files.push((name.to_string(), contents.to_string()));
         self
@@ -162,7 +169,11 @@ impl Package {
         };
 
         // Write file/line in the index
-        let dst = registry_path().join(&file);
+        let dst = if self.local {
+            registry_path().join("index").join(&file)
+        } else {
+            registry_path().join(&file)
+        };
         let mut prev = String::new();
         let _ = File::open(&dst).and_then(|mut f| f.read_to_string(&mut prev));
         t!(fs::create_dir_all(dst.parent().unwrap()));
@@ -170,20 +181,22 @@ impl Package {
                   .write_all((prev + &line[..] + "\n").as_bytes()));
 
         // Add the new file to the index
-        let repo = t!(git2::Repository::open(&registry_path()));
-        let mut index = t!(repo.index());
-        t!(index.add_path(Path::new(&file)));
-        t!(index.write());
-        let id = t!(index.write_tree());
-
-        // Commit this change
-        let tree = t!(repo.find_tree(id));
-        let sig = t!(repo.signature());
-        let parent = t!(repo.refname_to_id("refs/heads/master"));
-        let parent = t!(repo.find_commit(parent));
-        t!(repo.commit(Some("HEAD"), &sig, &sig,
-                       "Another commit", &tree,
-                       &[&parent]));
+        if !self.local {
+            let repo = t!(git2::Repository::open(&registry_path()));
+            let mut index = t!(repo.index());
+            t!(index.add_path(Path::new(&file)));
+            t!(index.write());
+            let id = t!(index.write_tree());
+
+            // Commit this change
+            let tree = t!(repo.find_tree(id));
+            let sig = t!(repo.signature());
+            let parent = t!(repo.refname_to_id("refs/heads/master"));
+            let parent = t!(repo.find_commit(parent));
+            t!(repo.commit(Some("HEAD"), &sig, &sig,
+                           "Another commit", &tree,
+                           &[&parent]));
+        }
     }
 
     fn make_archive(&self) {
@@ -233,7 +246,12 @@ impl Package {
     }
 
     pub fn archive_dst(&self) -> PathBuf {
-        dl_path().join(&self.name).join(&self.vers).join("download")
+        if self.local {
+            registry_path().join(format!("{}-{}.crate", self.name,
+                                         self.vers))
+        } else {
+            dl_path().join(&self.name).join(&self.vers).join("download")
+        }
     }
 }
 
diff --git a/tests/local-registry.rs b/tests/local-registry.rs
new file mode 100644 (file)
index 0000000..906e7cd
--- /dev/null
@@ -0,0 +1,343 @@
+#[macro_use]
+extern crate cargotest;
+extern crate hamcrest;
+
+use std::fs::{self, File};
+use std::io::prelude::*;
+
+use cargotest::support::paths::{self, CargoPathExt};
+use cargotest::support::registry::Package;
+use cargotest::support::{project, execs};
+use hamcrest::assert_that;
+
+fn setup() {
+    let root = paths::root();
+    t!(fs::create_dir(&root.join(".cargo")));
+    t!(t!(File::create(root.join(".cargo/config"))).write_all(br#"
+        [source.crates-io]
+        registry = 'https://wut'
+        replace-with = 'my-awesome-local-registry'
+
+        [source.my-awesome-local-registry]
+        local-registry = 'registry'
+    "#));
+}
+
+#[test]
+fn simple() {
+    setup();
+    Package::new("foo", "0.0.1")
+            .local(true)
+            .file("src/lib.rs", "pub fn foo() {}")
+            .publish();
+
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "bar"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "0.0.1"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+            pub fn bar() {
+                foo::foo();
+            }
+        "#);
+
+    assert_that(p.cargo_process("build"),
+                execs().with_status(0).with_stderr(&format!("\
+[UNPACKING] foo v0.0.1 ([..])
+[COMPILING] foo v0.0.1
+[COMPILING] bar v0.0.1 ({dir})
+",
+        dir = p.url())));
+    assert_that(p.cargo("build"), execs().with_status(0).with_stderr(""));
+    assert_that(p.cargo("test"), execs().with_status(0));
+}
+
+#[test]
+fn multiple_versions() {
+    setup();
+    Package::new("foo", "0.0.1").local(true).publish();
+    Package::new("foo", "0.1.0")
+            .local(true)
+            .file("src/lib.rs", "pub fn foo() {}")
+            .publish();
+
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "bar"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "*"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+            pub fn bar() {
+                foo::foo();
+            }
+        "#);
+
+    assert_that(p.cargo_process("build"),
+                execs().with_status(0).with_stderr(&format!("\
+[UNPACKING] foo v0.1.0 ([..])
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.0.1 ({dir})
+",
+        dir = p.url())));
+
+    Package::new("foo", "0.2.0")
+            .local(true)
+            .file("src/lib.rs", "pub fn foo() {}")
+            .publish();
+
+    assert_that(p.cargo("update").arg("-v"),
+                execs().with_status(0).with_stderr("\
+[UPDATING] foo v0.1.0 -> v0.2.0
+"));
+}
+
+#[test]
+fn multiple_names() {
+    setup();
+    Package::new("foo", "0.0.1")
+            .local(true)
+            .file("src/lib.rs", "pub fn foo() {}")
+            .publish();
+    Package::new("bar", "0.1.0")
+            .local(true)
+            .file("src/lib.rs", "pub fn bar() {}")
+            .publish();
+
+    let p = project("local")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "local"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "*"
+            bar = "*"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+            extern crate bar;
+            pub fn local() {
+                foo::foo();
+                bar::bar();
+            }
+        "#);
+
+    assert_that(p.cargo_process("build"),
+                execs().with_status(0).with_stderr(&format!("\
+[UNPACKING] [..]
+[UNPACKING] [..]
+[COMPILING] [..]
+[COMPILING] [..]
+[COMPILING] local v0.0.1 ({dir})
+",
+        dir = p.url())));
+}
+
+#[test]
+fn interdependent() {
+    setup();
+    Package::new("foo", "0.0.1")
+            .local(true)
+            .file("src/lib.rs", "pub fn foo() {}")
+            .publish();
+    Package::new("bar", "0.1.0")
+            .local(true)
+            .dep("foo", "*")
+            .file("src/lib.rs", "extern crate foo; pub fn bar() {}")
+            .publish();
+
+    let p = project("local")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "local"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "*"
+            bar = "*"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+            extern crate bar;
+            pub fn local() {
+                foo::foo();
+                bar::bar();
+            }
+        "#);
+
+    assert_that(p.cargo_process("build"),
+                execs().with_status(0).with_stderr(&format!("\
+[UNPACKING] [..]
+[UNPACKING] [..]
+[COMPILING] foo v0.0.1
+[COMPILING] bar v0.1.0
+[COMPILING] local v0.0.1 ({dir})
+",
+        dir = p.url())));
+}
+
+#[test]
+fn path_dep_rewritten() {
+    setup();
+    Package::new("foo", "0.0.1")
+            .local(true)
+            .file("src/lib.rs", "pub fn foo() {}")
+            .publish();
+    Package::new("bar", "0.1.0")
+            .local(true)
+            .dep("foo", "*")
+            .file("Cargo.toml", r#"
+                [project]
+                name = "bar"
+                version = "0.1.0"
+                authors = []
+
+                [dependencies]
+                foo = { path = "foo", version = "*" }
+            "#)
+            .file("src/lib.rs", "extern crate foo; pub fn bar() {}")
+            .file("foo/Cargo.toml", r#"
+                [project]
+                name = "foo"
+                version = "0.0.1"
+                authors = []
+            "#)
+            .file("foo/src/lib.rs", "pub fn foo() {}")
+            .publish();
+
+    let p = project("local")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "local"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "*"
+            bar = "*"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+            extern crate bar;
+            pub fn local() {
+                foo::foo();
+                bar::bar();
+            }
+        "#);
+
+    assert_that(p.cargo_process("build"),
+                execs().with_status(0).with_stderr(&format!("\
+[UNPACKING] [..]
+[UNPACKING] [..]
+[COMPILING] foo v0.0.1
+[COMPILING] bar v0.1.0
+[COMPILING] local v0.0.1 ({dir})
+",
+        dir = p.url())));
+}
+
+#[test]
+fn invalid_dir_bad() {
+    setup();
+    let p = project("local")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "local"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "*"
+        "#)
+        .file("src/lib.rs", "")
+        .file(".cargo/config", r#"
+            [source.crates-io]
+            registry = 'https://wut'
+            replace-with = 'my-awesome-local-directory'
+
+            [source.my-awesome-local-directory]
+            local-registry = '/path/to/nowhere'
+        "#);
+
+
+    assert_that(p.cargo_process("build"),
+                execs().with_status(101).with_stderr("\
+[ERROR] failed to load source for a dependency on `foo`
+
+Caused by:
+  Unable to update registry https://[..]
+
+Caused by:
+  failed to update replaced source `registry https://[..]`
+
+Caused by:
+  local registry path is not a directory: [..]path[..]to[..]nowhere
+"));
+}
+
+#[test]
+fn different_directory_replacing_the_registry_is_bad() {
+    setup();
+
+    // Move our test's .cargo/config to a temporary location and publish a
+    // registry package we're going to use first.
+    let config = paths::root().join(".cargo");
+    let config_tmp = paths::root().join(".cargo-old");
+    t!(fs::rename(&config, &config_tmp));
+
+    let p = project("local")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "local"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies]
+            foo = "*"
+        "#)
+        .file("src/lib.rs", "");
+    p.build();
+
+    // Generate a lock file against the crates.io registry
+    Package::new("foo", "0.0.1").publish();
+    assert_that(p.cargo("build"), execs().with_status(0));
+
+    // Switch back to our directory source, and now that we're replacing
+    // crates.io make sure that this fails because we're replacing with a
+    // different checksum
+    config.rm_rf();
+    t!(fs::rename(&config_tmp, &config));
+    Package::new("foo", "0.0.1")
+            .file("src/lib.rs", "invalid")
+            .local(true)
+            .publish();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(101).with_stderr("\
+[ERROR] checksum for `foo v0.0.1` changed between lock files
+
+this could be indicative of a few possible errors:
+
+    * the lock file is corrupt
+    * a replacement source in use (e.g. a mirror) returned a different checksum
+    * the source itself may be corrupt in one way or another
+
+unable to verify that `foo v0.0.1` was the same as before in any situation
+
+"));
+}
index b4d47358248fe2078008e6c92137363663110830..0ddb55999d561b3d4231d0d24d5076970ea73e76 100644 (file)
@@ -160,7 +160,7 @@ fn bad_cksum() {
 
     let pkg = Package::new("bad-cksum", "0.0.1");
     pkg.publish();
-    File::create(&pkg.archive_dst()).unwrap();
+    t!(File::create(&pkg.archive_dst()));
 
     assert_that(p.cargo_process("build").arg("-v"),
                 execs().with_status(101).with_stderr("\
@@ -418,7 +418,7 @@ fn yanks_in_lockfiles_are_ok() {
     assert_that(p.cargo("build"),
                 execs().with_status(0));
 
-    fs::remove_dir_all(&registry::registry_path().join("3")).unwrap();
+    registry::registry_path().join("3").rm_rf();
 
     Package::new("bar", "0.0.1").yanked(true).publish();
 
@@ -575,7 +575,7 @@ fn dev_dependency_not_used() {
 #[test]
 fn login_with_no_cargo_dir() {
     let home = paths::home().join("new-home");
-    fs::create_dir(&home).unwrap();
+    t!(fs::create_dir(&home));
     assert_that(cargo_process().arg("login").arg("foo").arg("-v"),
                 execs().with_status(0));
 }
@@ -640,7 +640,7 @@ fn updating_a_dep() {
 ",
    dir = p.url())));
 
-    t!(File::create(&p.root().join("a/Cargo.toml"))).write_all(br#"
+    t!(t!(File::create(&p.root().join("a/Cargo.toml"))).write_all(br#"
         [project]
         name = "a"
         version = "0.0.1"
@@ -648,7 +648,7 @@ fn updating_a_dep() {
 
         [dependencies]
         bar = "0.1.0"
-    "#).unwrap();
+    "#));
     Package::new("bar", "0.1.0").publish();
 
     println!("second");
@@ -740,7 +740,7 @@ fn update_publish_then_update() {
     Package::new("a", "0.1.1").publish();
     let registry = paths::home().join(".cargo/registry");
     let backup = paths::root().join("registry-backup");
-    fs::rename(&registry, &backup).unwrap();
+    t!(fs::rename(&registry, &backup));
 
     // Generate a Cargo.lock with the newer version, and then move the old copy
     // of the registry back into place.
@@ -757,9 +757,9 @@ fn update_publish_then_update() {
         .file("src/main.rs", "fn main() {}");
     assert_that(p2.cargo_process("build"),
                 execs().with_status(0));
-    fs::remove_dir_all(&registry).unwrap();
-    fs::rename(&backup, &registry).unwrap();
-    fs::rename(p2.root().join("Cargo.lock"), p.root().join("Cargo.lock")).unwrap();
+    registry.rm_rf();
+    t!(fs::rename(&backup, &registry));
+    t!(fs::rename(p2.root().join("Cargo.lock"), p.root().join("Cargo.lock")));
 
     // Finally, build the first project again (with our newer Cargo.lock) which
     // should force an update of the old registry, download the new crate, and
@@ -1125,16 +1125,12 @@ fn disallow_network() {
 
     assert_that(p.cargo("build").arg("--frozen"),
                 execs().with_status(101).with_stderr("\
-[UPDATING] registry `[..]`
 error: failed to load source for a dependency on `foo`
 
 Caused by:
   Unable to update registry [..]
 
 Caused by:
-  failed to fetch `[..]`
-
-Caused by:
-  attempting to update a git repository, but --frozen was specified
+  attempting to make an HTTP request, but --frozen was specified
 "));
 }